/*
 * Copyright 2010 Biz-e (info@biz-e.nl)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.biz_e.gwt.baselib.ui.client.widgets;

import java.util.ArrayList;
import java.util.List;

import nl.biz_e.gwt.baselib.ui.client.widgets.resource.Images;
import nl.biz_e.gwt.baselib.ui.client.widgets.resource.WidgetStrings;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.RichTextArea;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A toolbar to control the {@link RichTextArea}. Provides most of the features
 * the area provides, plus the possibility to edit the HTML source code
 * directly.<br/>
 * <br/>
 * <h3>CSS styles used by this class:</h3>
 * 
 * <pre>
 * .richTextEditor {
 *   border: 1px solid #BBBBBB;
 *   border-spacing: 0px;
 * }
 * 
 * .RTEditorToggleButton {
 *   padding: 1px;
 *   border: 0px;
 * }
 * 
 * .RTEditorToggleButton:focus {
 *   outline: none;
 * }
 * 
 * .RTEditorToggleButton-up-hovering {
 *   border: 1px;
 *   border-style: outset;
 *   padding: 0px;
 * }
 * 
 * .RTEditorToggleButton-down-hovering {
 *   border: 1px;
 *   border-style: inset;
 *   padding: 0px;
 * }
 * 
 * .RTEditorToggleButton-down {
 *   border: 1px;
 *   border-style: inset;
 *   padding: 0px;
 * }
 * 
 * .RTEditorPushButton {
 *   padding: 1px;
 *   border: 0px;
 * }
 * 
 * .RTEditorPushButton:focus {
 *   outline: none;
 * }
 * 
 * .RTEditorPushButton-up-hovering {
 *   border: 1px;
 *   border-style: outset;
 *   padding: 0px;
 * }
 * 
 * .RTEditorPushButton-down-hovering {
 *   border: 1px;
 *   border-style: inset;
 *   padding: 0px;
 * }
 * 
 * .opacity_half {
 *   opacity:0.4;
 *   filter:alpha(opacity=40)
 * }
 * </pre>
 * 
 * Add these to your own css file.
 * <p>
 * This toolbar is heavily based on the toolbar provided by the samples of the
 * GWT SDK
 * </p>
 * 
 * @author Joost A. Bloemsma
 */
class RichTextToolbar extends Composite {
    /**
     * We use an inner EventHandler class to avoid exposing event methods on the
     * RichTextToolbar itself.
     */
    private class EventHandler implements ClickHandler, ChangeHandler,
            KeyUpHandler {

        public void onChange(ChangeEvent event) {
            Widget sender = (Widget) event.getSource();

            if (sender == backColors) {
                formatter.setBackColor(backColors.getValue(backColors
                        .getSelectedIndex()));
                backColors.setSelectedIndex(0);
            } else if (sender == foreColors) {
                formatter.setForeColor(foreColors.getValue(foreColors
                        .getSelectedIndex()));
                foreColors.setSelectedIndex(0);
            } else if (sender == fonts) {
                formatter.setFontName(fonts.getValue(fonts.getSelectedIndex()));
                fonts.setSelectedIndex(0);
            } else if (sender == fontSizes) {
                formatter.setFontSize(fontSizesConstants[fontSizes
                        .getSelectedIndex() - 1]);
                fontSizes.setSelectedIndex(0);
            }
        }

        public void onClick(ClickEvent event) {
            Widget sender = (Widget) event.getSource();

            if (sender == bold) {
                formatter.toggleBold();
            } else if (sender == italic) {
                formatter.toggleItalic();
            } else if (sender == underline) {
                formatter.toggleUnderline();
            } else if (sender == subscript) {
                formatter.toggleSubscript();
            } else if (sender == superscript) {
                formatter.toggleSuperscript();
            } else if (sender == strikethrough) {
                formatter.toggleStrikethrough();
            } else if (sender == indent) {
                formatter.rightIndent();
            } else if (sender == outdent) {
                formatter.leftIndent();
            } else if (sender == justifyLeft) {
                formatter.setJustification(RichTextArea.Justification.LEFT);
            } else if (sender == justifyCenter) {
                formatter.setJustification(RichTextArea.Justification.CENTER);
            } else if (sender == justifyRight) {
                formatter.setJustification(RichTextArea.Justification.RIGHT);
            } else if (sender == insertImage) {
                String url = Window.prompt(strings.enterImageUrl(), "http://");
                if (url != null) {
                    formatter.insertImage(url);
                }
            } else if (sender == createLink) {
                String url = Window.prompt(strings.enterLinkUrl(), "http://");
                if (url != null) {
                    formatter.createLink(url);
                }
            } else if (sender == removeLink) {
                formatter.removeLink();
            } else if (sender == hr) {
                formatter.insertHorizontalRule();
            } else if (sender == ol) {
                formatter.insertOrderedList();
            } else if (sender == ul) {
                formatter.insertUnorderedList();
            } else if (sender == removeFormat) {
                formatter.removeFormat();
            } else if (sender == richText) {
                // We use the RichTextArea's onKeyUp event to update the toolbar
                // status.
                // This will catch any cases where the user moves the cursur
                // using the
                // keyboard, or uses one of the browser's built-in keyboard
                // shortcuts.
                updateStatus();
            } else if (sender == editHtml) {
                editSource(editHtml.isDown());
            }

        }

        public void onKeyUp(KeyUpEvent event) {
            Widget sender = (Widget) event.getSource();
            if (sender == richText) {
                // We use the RichTextArea's onKeyUp event to update the toolbar
                // status.
                // This will catch any cases where the user moves the cursur
                // using the
                // keyboard, or uses one of the browser's built-in keyboard
                // shortcuts.
                updateStatus();
            }
        }
    }

    private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
            RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
            RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
            RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
            RichTextArea.FontSize.XX_LARGE };

    private Images images = (Images) GWT.create(Images.class);
    private WidgetStrings strings = (WidgetStrings) GWT
            .create(WidgetStrings.class);
    private EventHandler handler = new EventHandler();

    private RichTextArea richText;
    private RichTextArea.Formatter formatter;
    private RichTextEditor editor;

    private VerticalPanel outer = new VerticalPanel();
    private HorizontalPanel topPanel = new HorizontalPanel();
    private HorizontalPanel bottomPanel = new HorizontalPanel();
    private ToggleButton bold;
    private ToggleButton italic;
    private ToggleButton underline;
    private ToggleButton subscript;
    private ToggleButton superscript;
    private ToggleButton strikethrough;
    private ToggleButton editHtml;
    private PushButton indent;
    private PushButton outdent;
    private PushButton justifyLeft;
    private PushButton justifyCenter;
    private PushButton justifyRight;
    private PushButton hr;
    private PushButton ol;
    private PushButton ul;
    private PushButton insertImage;
    private PushButton createLink;
    private PushButton removeLink;
    private PushButton removeFormat;

    private ListBox backColors;
    private ListBox foreColors;
    private ListBox fonts;
    private ListBox fontSizes;

    private final List<RteToolbarComponent> widgets = new ArrayList<RteToolbarComponent>();

    /**
     * Creates a new toolbar that drives the given rich text area.
     * 
     * @param editor
     *            the {@link RichTextEditor} to be controlled
     */
    public RichTextToolbar(RichTextEditor editor) {
        this.editor = editor;
        this.richText = editor.getArea();
        this.formatter = richText.getFormatter();

        outer.add(topPanel);
        outer.add(bottomPanel);
        topPanel.setWidth("100%");
        bottomPanel.setWidth("100%");

        initWidget(outer);
        setStyleName("gwt-RichTextToolbar");
        richText.addStyleName("hasRichTextToolbar");

        if (formatter != null) {
            topPanel.add(bold = createToggleButton(images.bold(), strings
                    .bold()));
            topPanel.add(italic = createToggleButton(images.italic(), strings
                    .italic()));
            topPanel.add(underline = createToggleButton(images.underline(),
                    strings.underline()));
            topPanel.add(subscript = createToggleButton(images.subscript(),
                    strings.subscript()));
            topPanel.add(superscript = createToggleButton(images.superscript(),
                    strings.superscript()));
            topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
                    strings.justifyLeft()));
            topPanel.add(justifyCenter = createPushButton(images
                    .justifyCenter(), strings.justifyCenter()));
            topPanel.add(justifyRight = createPushButton(images.justifyRight(),
                    strings.justifyRight()));
            topPanel.add(strikethrough = createToggleButton(images
                    .strikeThrough(), strings.strikeThrough()));
            topPanel.add(indent = createPushButton(images.indent(), strings
                    .indent()));
            topPanel.add(outdent = createPushButton(images.outdent(), strings
                    .outdent()));
            topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
            topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
            topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
            topPanel.add(insertImage = createPushButton(images.insertImage(),
                    strings.insertImage()));
            topPanel.add(createLink = createPushButton(images.createLink(),
                    strings.createLink()));
            topPanel.add(removeLink = createPushButton(images.removeLink(),
                    strings.removeLink()));
            topPanel.add(removeFormat = createPushButton(images.removeFormat(),
                    strings.removeFormat()));
        }

        if (formatter != null) {
            bottomPanel.add(backColors = createColorList(strings.backColor()));
            bottomPanel.add(foreColors = createColorList(strings.foreColor()));
            bottomPanel.add(fonts = createFontList());
            bottomPanel.add(fontSizes = createFontSizes());
            bottomPanel.add(editHtml = createToggleButton(images.editSource(),
                    strings.editSource()));
            // We only use these handlers for updating status, so don't hook
            // them up
            // unless at least basic editing is supported.
            richText.addKeyUpHandler(handler);
            richText.addClickHandler(handler);
        }
    }

    private void editSource(boolean source) {
        if (source) {
            editor.setState(RichTextEditor.State.SOURCE);
        } else {
            editor.setState(RichTextEditor.State.RICH);
        }
        for (RteToolbarComponent w : widgets) {
            if (w != editHtml)
                w.setCompEnabled(!source);
        }
    }

    private RteListBox createColorList(String caption) {
        RteListBox lb = new RteListBox();
        lb.addChangeHandler(handler);
        lb.setVisibleItemCount(1);

        lb.addItem(caption);
        lb.addItem(strings.white(), "white");
        lb.addItem(strings.black(), "black");
        lb.addItem(strings.red(), "red");
        lb.addItem(strings.green(), "green");
        lb.addItem(strings.yellow(), "yellow");
        lb.addItem(strings.blue(), "blue");
        widgets.add(lb);
        return lb;
    }

    private RteListBox createFontList() {
        RteListBox lb = new RteListBox();
        lb.addChangeHandler(handler);
        lb.setVisibleItemCount(1);

        lb.addItem(strings.font(), "");
        lb.addItem(strings.normal(), "");
        lb.addItem("Times New Roman", "Times New Roman");
        lb.addItem("Arial", "Arial");
        lb.addItem("Courier New", "Courier New");
        lb.addItem("Georgia", "Georgia");
        lb.addItem("Trebuchet", "Trebuchet");
        lb.addItem("Verdana", "Verdana");
        widgets.add(lb);
        return lb;
    }

    private RteListBox createFontSizes() {
        RteListBox lb = new RteListBox();
        lb.addChangeHandler(handler);
        lb.setVisibleItemCount(1);

        lb.addItem(strings.size());
        lb.addItem(strings.xxsmall());
        lb.addItem(strings.xsmall());
        lb.addItem(strings.small());
        lb.addItem(strings.medium());
        lb.addItem(strings.large());
        lb.addItem(strings.xlarge());
        lb.addItem(strings.xxlarge());
        widgets.add(lb);
        return lb;
    }

    private RtePushButton createPushButton(ImageResource img, String tip) {
        Image image = new Image(img);

        RtePushButton pb = new RtePushButton(image);
        pb.addClickHandler(handler);
        pb.setTitle(tip);
        pb.setSize("20px", "20px");
        pb.setStylePrimaryName("biz_eRTEditorPushButton");
        widgets.add(pb);
        return pb;
    }

    private RteToggleButton createToggleButton(ImageResource img, String tip) {
        RteToggleButton tb = new RteToggleButton(new Image(img));
        tb.addClickHandler(handler);
        tb.setTitle(tip);
        tb.setSize("20px", "20px");
        tb.setStylePrimaryName("biz_eRTEditorToggleButton");
        widgets.add(tb);
        return tb;
    }

    /**
     * Updates the status of all the stateful buttons.
     */
    private void updateStatus() {
        if (formatter != null) {
            bold.setDown(formatter.isBold());
            italic.setDown(formatter.isItalic());
            underline.setDown(formatter.isUnderlined());
            subscript.setDown(formatter.isSubscript());
            superscript.setDown(formatter.isSuperscript());
            strikethrough.setDown(formatter.isStrikethrough());
        }
    }

    private interface RteToolbarComponent {
        public void setCompEnabled(boolean enabled);
    }

    private class RteListBox extends ListBox implements RteToolbarComponent {
        /**
         * {@inheritDoc}
         */
        @Override
        public void setCompEnabled(boolean enabled) {
            this.setEnabled(enabled);
        }
    }

    private class RteToggleButton extends ToggleButton implements
            RteToolbarComponent {
        private Image image;

        /**
         * {@inheritDoc}
         */
        @Override
        public void setCompEnabled(boolean enabled) {
            this.setEnabled(enabled);
            if (enabled) {
                image.removeStyleName("opacity_half");
            } else {
                image.addStyleName("opacity_half");
            }
        }

        /**
         * Constructs a new RtePushButton instance
         * 
         * @param image
         */
        public RteToggleButton(Image image) {
            super(image);
            this.image = image;
        }
    }

    private class RtePushButton extends PushButton implements
            RteToolbarComponent {
        private Image image;

        /**
         * {@inheritDoc}
         */
        @Override
        public void setCompEnabled(boolean enabled) {
            this.setEnabled(enabled);
            if (enabled) {
                image.removeStyleName("opacity_half");
            } else {
                image.addStyleName("opacity_half");
            }
        }

        /**
         * Constructs a new RtePushButton instance
         * 
         * @param image
         */
        public RtePushButton(Image image) {
            super(image);
            this.image = image;
        }
    }
}
